#!/usr/bin/python -S
"""
parse.py
"""

__author__ = 'Andy Chu'


import re
import sys

# This package
import _exceptions
import execute
import tokenize  # JSON Pattern, not stdlib


class Subpattern(object):

  def __init__(self, *args, **kwargs):
    self.args = args
    self.kwargs = kwargs

# Is there a better place to put this?
_DEFAULT_PATTERNS = {
    'int': Subpattern('P| { \d+ |int}'),
    'H:M:S': Subpattern('P| {:int hour}:{:int minute}:{:int second}'),
    'H:M': Subpattern('P| {:int hour}:{:int minute}'),
    'Y/M/D': Subpattern('P| {:int year}/{:int month}/{:int day}'),
    'M/D/Y': Subpattern('P| {:int month}/{:int day}/{:int year}'),
    }


def MakeObject(pairs):
  obj = {}
  for p in pairs:
    print p, '!!!'
    obj[p["name"]] = p["value"]
  return obj


# Just define a few "nicer" aliases for Python builtins.  This should be
# specified more strictly.

_DEFAULT_FILTERS = {
    # Simple type conversion functions
    # TODO: Could have stuff for date/time parsing, etc.
    'int': int,
    'comma-int': lambda x: int(x.replace(',', '')),

    # Hex string to integer
    'hex2int': lambda x: int(x, 16),
    # Octal string to integer
    'oct2int': lambda x: int(x, 8),

    # Integer ASCII code -> character
    'chr': chr,

    'bool': bool,
    'float': float,

    # Works on lists, dictionaries
    'len': len,

    'join-strings': lambda lst: ''.join(lst),

    'make-obj': MakeObject,
    }


def Push(value, stack):
  stack.append(value)
  return value

_DEFAULT_ACTIONS = {
  # Workspace is a stack
  'push!': Push,
  }



VARNAME_FRAG = r"""
    \s*
    ([^|\s]+)?      # variable name contains neither | nor whitespace
    \s*             # allow trailing space without filter
    """

# With group blocks, we can't use scalar capture.  The variable name is optional
# as usual.  We allow the user to say groups[] or group[1], for readability.
GROUP_CAPTURE_RE = re.compile(r"""
    groups?\[(\d+)?\]
    """ + VARNAME_FRAG, re.VERBOSE)

# e.g. 'value foo', 'optional $bar'
BLOCK_RE = re.compile(r"""
    (\$?)(optional|choice|value|repeated)\b
    (?: \s+ ([\d,*+]+) )?  # optional repeats (only valid for 'repeated')
    """ + VARNAME_FRAG, re.VERBOSE)

SUBPATTERN_RE = re.compile(r':(\S+)' + VARNAME_FRAG, re.VERBOSE)

SHORTHAND_RE = re.compile(VARNAME_FRAG, re.VERBOSE)


FILTER_RE = re.compile(r'\s* \| \s* ([^|\s]+)', re.VERBOSE)

def ParseFilters(token, pos=0):
  """Grab a list of filters from the end of the token.

  TODO: Assert $ at the end?
  """
  filters = []
  while True:
    match = FILTER_RE.match(token, pos)
    if match:
      filters.append(match.group(1))
      pos = match.end()
    else:
      break
  return filters


class _Block(object):
  """Represents a list of fragments (which themselves may be blocks)."""

  def __init__(self, name, scalar=False, filters=None):
    """
    Args:
      name: variable name to capture
    """
    self.name = name
    # Whether to do "scalar" capture, instead of composite capture within the
    # block
    self.scalar = scalar
    self.filters = filters or []

  def Fragments(self):
    """Retrieve the fragments that this block contains."""
    raise NotImplementedError

  def Append(self, ins):
    """Append an instruction to this block."""
    raise NotImplementedError

  def __repr__(self):
    class_name = self.__class__.__name__[1:]
    pieces = []
    if self.name:
      pieces.append('name: %s' % self.name)
    else:
      pieces.append('anonymous')

    if self.filters:
      pieces.append(
          'filters: %s' % ','.join(f.__name__ for (_, f) in self.filters))

    if self.scalar:
      pieces.append('scalar')

    return '(' + '; '.join(str(p) for p in pieces) + ')'

  def AsTree(self, indent=0):
    raise NotImplementedError


def _LinesForArg(func, arg, indent):
  """Show the various argument types as text as a list of lines."""
  #print 'calling _LinesForArg with indent %s' % indent
  func_name = func.__name__[1:]  # Remove leading _
  leading = indent * ' '
  #print func, arg, indent

  if isinstance(arg, basestring):   # literal
    lines = [leading + '%s %r' % (func_name, arg)]

  elif hasattr(arg, 'match'):   # regex
    s = '%s %r' % (func_name, arg.pattern)
    if arg.flags:
      s += ' flags: %s' % arg.flags
    lines = [leading + s]

  elif isinstance(arg, tuple):   # name, subpattern
    name, pattern = arg
    # Won't work because of nested patterns
    #result = pattern.AsTree(indent=indent+4)
    lines = [leading + '%s %s' % (func_name, pattern)]

  else:
    block = arg
    lines = block.AsTree(indent+4)
    # Insert the function name after the indentation
    lines[0] = lines[0][:indent] + func_name + lines[0][indent:]

  # This is hard to get right, because of the indentation above
  for line in lines:
    assert line.startswith(indent * ' '), (repr(line), indent)

  return lines


class _ValueBlock(_Block):
  """value and optional blocks have the same representation.

  Represents a list of fragments (which themselves may be blocks).
  """

  def __init__(self, *args, **kwargs):
    """
    Args:
      name: variable name to capture
    """
    _Block.__init__(self, *args, **kwargs)

    # Pairs of func, args, or a literal string
    self.fragments = []

  def Fragments(self, clause='default'):
    return self.fragments

  def Append(self, ins):
    """Append an instruction to this block."""
    self.fragments.append(ins)

  def NewClause(self):
    # Catches the case when someone tries to add a clause to a non-choice block
    raise _exceptions.PatternSyntaxError(
        "Block type %s doesn't have clauses" % self.__class__.__name)

  def AsTree(self, indent=0):
    #print 'calling AsTree with indent %s' % indent

    if indent == 0:  
      # Don't care about the initial block, as it's always the same
      lines = []
    else:
      lines = [indent * ' ' + repr(self)]

    for func, arg in self.Fragments():
      lines.extend(_LinesForArg(func, arg, indent))

    for line in lines:
      assert line.startswith(indent * ' '), (repr(line), indent)
      
    if indent == 0:
      return '\n'.join(lines) + '\n'
    else:
      return lines


class _Repeated(_ValueBlock):
  """Represents a repeated block."""

  def __init__(self, repeats, *args, **kwargs):
    """
    Args:
      repeats: A 2-tuple for min/max repeats, e.g. (3, None)
    """
    _ValueBlock.__init__(self, *args, **kwargs)
    self.repeats = repeats


class _GroupCapture(_ValueBlock):
  """A pseudo-block, which can only contain a regular expression."""

  def __init__(self, index, *args, **kwargs):
    """
    Args:
      index: Index of the group to capture, i.e.  1, 2, 3, ... or None for all
          groups.
    """
    _ValueBlock.__init__(self, *args, **kwargs)
    self.index = index


class _Subpattern(_ValueBlock):
  """A pseudo-block, which can only has a regex as a container."""

  def __init__(self, pattern, *args, **kwargs):
    """
    Args:
      pattern: A Pattern object.
    """
    _ValueBlock.__init__(self, *args, **kwargs)
    self.pattern = pattern


class _Choices(_Block):
  """Represents a choices block."""

  def __init__(self, *args, **kwargs):
    _Block.__init__(self, *args, **kwargs)
    self.current_clause = []
    self.fragments_list = [self.current_clause]

  def Fragments(self):
    return self.fragments_list

  def Append(self, ins):
    self.current_clause.append(ins)

  def NewClause(self):
    self.current_clause = []
    self.fragments_list.append(self.current_clause)

  def AsTree(self, indent=0):
    leading = indent * ' '
    leading_or = (indent-4) * ' '  # A bit of a hack

    lines = [leading + repr(self)]
    for fragments in self.fragments_list:
      for func, arg in fragments:
        lines.extend(_LinesForArg(func, arg, indent))
      lines.append(leading_or + 'or')
    return lines


def log(s, *args):
  print '***', s % args


ACTION, PATTERN, FILTER = 0, 1, 2

class ProgramBuilder(object):
  """
  Receives method calls from the tokenizer, and constructs a tree of _Block()
  instances.
  """

  def __init__(self, patterns={}, filters={}, actions={}):
    """
    Args:
      patterns: A mapping from names to regular expressions.  Can be referenced
      with :name: or !name!.
    """
    self.current_block = _ValueBlock('')  # An anonymous value
    self.stack = [self.current_block]

    # Passing a dictionary instead of a function is often more convenient
    # .get is a function which returns either a function None
    if isinstance(patterns, dict):
      patterns = patterns.get
    self.patterns = patterns

    if isinstance(actions, dict):
      actions = actions.get
    self.actions = actions

    if isinstance(filters, dict):
      filters = filters.get
    self.filters = filters

  def _GetPattern(self, name):
    """Given a pattern name, return a regex fragment string.

    The user's patterns are consulted first, then the default formatters.
    """
    pattern = self.patterns(name) or _DEFAULT_PATTERNS.get(name)

    # Compile it if necessary
    # TODO: Should have a cache of compiled patterns.  Maybe mutate
    # _DEFAULT_PATTERNS?
    import _jsonpattern  # HACK: break circular depenedency
    if isinstance(pattern, Subpattern):  
      pattern = _jsonpattern.Pattern(pattern.args[0], **pattern.kwargs)

    if pattern:
      return pattern
    else:
      raise _exceptions.BadPattern("Can't find named pattern %r" % name)

  def _GetFilters(self, names):
    """Given a pattern name, return a regex fragment string.

    The user's patterns are consulted first, then the default formatters.
    """
    if not names:
      return []

    filters = []
    for name in names:
      if name.endswith('!'):
        func = self.actions(name) or _DEFAULT_ACTIONS.get(name)
        filter_type = ACTION
      elif name.startswith('~'):
        func = self.patterns(name) or _DEFAULT_PATTERNS.get(name)
        filter_type = PATTERN
      else:
        func = self.filters(name) or _DEFAULT_FILTERS.get(name)
        filter_type = FILTER

      if func:
        filters.append((filter_type, func))
      else:
        raise _exceptions.BadFilter("Can't find named filter %r" % name)
    return filters

  def _NewBlock(self, func, new_block):
    self.current_block.Append((func, new_block))
    self.stack.append(new_block)
    self.current_block = new_block

  #
  # Public
  #

  def AppendLiteral(self, s):
    """Append a literal string s to the current block."""
    self.current_block.Append((execute._MatchLiteral, s))

  def AppendRegex(self, regex, flags):
    """Append an (uncompiled) regex string to the current block."""
    try:
      regex = re.compile(regex, flags=flags)
    except re.error, e:
      raise _exceptions.PatternSyntaxError(
          "Error compiling regular expression %r : %s" % (regex, e))
    self.current_block.Append((execute._MatchRegex, regex))

  def AppendSubpattern(self, pattern_name, value_name, filter_names=None):
    pattern = self._GetPattern(pattern_name)
    filters = self._GetFilters(filter_names)
    # Should we ever allow scalar capture for subpatterns?
    block = _Subpattern(pattern, value_name, scalar=False, filters=filters)
    self.current_block.Append((execute._MatchSubpattern, block))

  def NewRepeatedSection(self, repeats, name, scalar=False, filter_names=None):
    """For repeated blocks."""

    filters = self._GetFilters(filter_names)
    new_block = _Repeated(repeats, name, scalar=scalar, filters=filters)
    self._NewBlock(execute._MatchRepeated, new_block)

  # TODO: The following 3 functions are identical
  def NewValue(self, name, scalar=False, filter_names=None):
    """For value blocks."""

    filters = self._GetFilters(filter_names)
    new_block = _ValueBlock(name, scalar=scalar, filters=filters)
    self._NewBlock(execute._MatchValue, new_block)

  def NewOptional(self, name, scalar=False, filter_names=None):
    """For optional blocks."""

    filters = self._GetFilters(filter_names)
    new_block = _ValueBlock(name, scalar=scalar, filters=filters)
    self._NewBlock(execute._MatchOptional, new_block)

  def NewChoice(self, name, scalar, filter_names=None):
    """For choice blocks."""

    filters = self._GetFilters(filter_names)
    new_block = _Choices(name, scalar, filters=filters)
    self._NewBlock(execute._MatchChoices, new_block)

  def NewClause(self):
    """For {or}"""
    self.current_block.NewClause()

  def NewGroupCapture(self, index, name, filter_names=None):
    """For group[1] ..."""
    if index == 0:
      raise _exceptions.PatternSyntaxError(
          "Use scalar capture with $ instead of group[0]")
    filters = self._GetFilters(filter_names)
    new_block = _GroupCapture(index, name, filters=filters)
    self._NewBlock(execute._MatchAndCaptureGroup, new_block)

  def End(self):
    """End a block."""
    self.stack.pop()
    self.current_block = self.stack[-1]

  def Root(self):
    # It's assumed that we call this at the end of the program
    return self.current_block


def Parse(s, tokens, builder):

  for token_type, token, flags in tokens:
    #print '-->', token_type, token

    if token_type == tokenize.LITERAL_TOKEN:
      builder.AppendLiteral(token)

    elif token_type == tokenize.REGEX_TOKEN:
      builder.AppendRegex(token, flags)

    elif token_type == tokenize.BLOCK_TOKEN:
      if token == 'or':
        builder.NewClause()
        continue

      if token == 'end':
        builder.End()
        continue

      match = BLOCK_RE.match(token)
      if match:
        scalar, block_type, repeats, name = match.groups()
        filter_names = ParseFilters(token, match.end()) 
        scalar = bool(scalar)  # '' or '$' -> False or True

        if block_type == 'repeated':
          if repeats:
            repeats = tokenize.ParseRepetitions(repeats)
          else:
            # * is the default type of repeat
            repeats = (0, None)
          builder.NewRepeatedSection(
              repeats, name, scalar=False, filter_names=filter_names)
          continue
        else:
          # Error checking
          if repeats:
            raise _exceptions.PatternSyntaxError(
                "%s block can't contain repeats (%s)" % (block_type, repeats))

        if block_type == 'value':
          # This just starts a new namespace
          builder.NewValue(name, scalar=scalar, filter_names=filter_names)
          continue

        if block_type == 'optional':
          builder.NewOptional(name, scalar=scalar, filter_names=filter_names)
          continue

        if block_type == 'choice':
          builder.NewChoice(name, scalar=scalar, filter_names=filter_names)
          continue

      match = GROUP_CAPTURE_RE.match(token)
      if match:
        index, name = match.groups()
        filter_names = ParseFilters(token, match.end()) 
        if index:
          index = int(index)

        # index is 1, 2, 3, ... or None for an array of all groups
        builder.NewGroupCapture(index, name, filter_names=filter_names)
        continue

      # Didn't match either of the regexes
      raise _exceptions.PatternSyntaxError('Invalid block token %r' % token)

    elif token_type == tokenize.INSTRUCTION_TOKEN:  # It's a {...} token

      if token == '':  # A no-op that allows control of whitespace
        continue

      if token.startswith('#'):  # comment
        continue

      # Didn't match any block type, must be a named capture

      if token.startswith(':'):
        match = SUBPATTERN_RE.match(token)
        if match:
          pattern_name, value_name = match.groups()
          filter_names = ParseFilters(token, match.end())
          builder.AppendSubpattern(
              pattern_name, value_name, filter_names=filter_names)
          continue

        # Only subpatterns can start with :
        raise _exceptions.PatternSyntaxError('Invalid subpattern %r' % token)

      # Not a subpattern; it must be a shorthand value, e.g. { \w+ name}
      try:
        delimiter = token[0]
      except IndexError:
        raise _exceptions.PatternSyntaxError('Got empty token %r' % token)

      end = token.find(delimiter, 1)
      if end == -1:
        raise _exceptions.PatternSyntaxError(
            "The regex wasn't terminated with delimiter %r (token %r)" %
            (delimiter, token))

      regex = token[1:end]

      match = SHORTHAND_RE.match(token, end+1)
      if not match:
        raise _exceptions.PatternSyntaxError('Invalid named capture %r' % token)

      name = match.group(1)
      filter_names = ParseFilters(token, match.end())

      # This syntax is just shorthand for the {/value name}\d+{/end} syntax
      builder.NewValue(name, scalar=True, filter_names=filter_names)
      builder.AppendRegex(regex, flags)
      builder.End()

  return builder.Root()
